﻿using System;
using System.Diagnostics;
using System.IO;
using System.Linq;

using Microsoft.Win32;

namespace Microsoft.Coding4Fun.TweetCraft.Wow
{
    /// <summary>
    /// Detects World of Warcraft installation using the Windows Registry.
    /// </summary>
    public class WorldOfWarcraft : IWorldOfWarcraft
    {
        private const string wowProcessName = "Wow";
        private const string wowLauncherName = "Launcher";
        private const string wowExecutableExtension = ".exe";

        private const string wowRegistryKeyName = @"SOFTWARE\Blizzard Entertainment\World of Warcraft";
        private const string installPathValueName = "InstallPath";
        private const string relativeAccountPath = @"WTF\Account";
        private const string relativeAddOnPath = @"Interface\AddOns";
        private const string savedVariablesRelativePath = "SavedVariables";
        private const string luaExtension = ".lua";

        private const string tocExtension = ".toc";

        /// <summary>
        /// Gets a value indicating whether a World of Warcraft installation is detected.
        /// </summary>
        public bool IsDetected
        {
            get
            {
                return InstallPath != null;
            }
        }

        /// <summary>
        /// Gets a value indicating whether World of Warcraft is running.
        /// </summary>
        public bool IsRunning
        {
            get
            {
                return Process.GetProcessesByName(wowProcessName).Length > 0;
            }
        }

        /// <summary>
        /// Gets a value indicating whether World of Warcraft accounts are available through <see cref="GetAccountNames"/>.
        /// </summary>
        public bool AreAccountsAvailable
        {
            get
            {
                return IsDetected && GetAccountNames().Length > 0;
            }
        }

        /// <summary>
        /// Gets the install path of World of Warcraft.
        /// </summary>
        /// <returns>
        /// A <see cref="string"/> containing the World of Warcraft installation path.
        /// </returns>
        public virtual string InstallPath
        {
            get
            {
                using (RegistryKey wowRegistryKey = Registry.LocalMachine.OpenSubKey(wowRegistryKeyName))
                {
                    if (wowRegistryKey == null)
                        return null;

                    return wowRegistryKey.GetValue(installPathValueName) as string;
                }
            }
        }

        /// <summary>
        /// Starts up World of Warcraft.
        /// </summary>
        public void Start()
        {
            if (IsRunning)
                throw new InvalidOperationException(Strings.WorldOfWarcraftAlreadyRunning);

            Process.Start(Path.Combine(InstallPath, wowLauncherName) + wowExecutableExtension);
        }

        /// <summary>
        /// Returns an array of the account names in World of Warcraft.
        /// </summary>
        /// <returns>
        /// An array of <see cref="string"/>s that contain the account names.
        /// </returns>
        public string[] GetAccountNames()
        {
            if (!this.IsDetected)
                throw new InvalidOperationException(Strings.InvalidOperationWorldOfWarcraftNotDetected);

            // Combine the install path with the relative account path
            string accountPath = Path.Combine(this.InstallPath, relativeAccountPath);
            if (Directory.Exists(accountPath))
                return Directory.GetDirectories(accountPath).Select(directory => Path.GetFileName(directory)).ToArray();
           

            return new string[0];
        }

        /// <summary>
        /// Returns an array of the addon names in World of Warcraft.
        /// </summary>
        /// <returns>
        /// An array of <see cref="string"/>s that contain the addon names.
        /// </returns>
        public string[] GetAddOnNames()
        {
            if (!this.IsDetected)
                throw new InvalidOperationException(Strings.InvalidOperationWorldOfWarcraftNotDetected);

            // Combine the install path with the relative addon path
            string addOnPath = Path.Combine(this.InstallPath, relativeAddOnPath);
            if (Directory.Exists(addOnPath))
                return Directory.GetDirectories(addOnPath).Select(directory => Path.GetFileName(directory)).ToArray();

            return new string[0];
        }

        public string[] GetRealmNames(string accountName)
        {
            if (accountName == null)
                throw new ArgumentNullException("accountName");
            if (!this.IsDetected)
                throw new InvalidOperationException(Strings.InvalidOperationWorldOfWarcraftNotDetected);

            // Get the path to the account
            string accountPath = GetAccountPath(accountName);

            // Return all subdirectories (representing realms) except the SavedVariables directory
            return Directory.GetDirectories(accountPath)
                .Select(directory => Path.GetFileName(directory))
                .Where(directory => String.Compare(directory, savedVariablesRelativePath, StringComparison.OrdinalIgnoreCase) != 0)
                .ToArray();
        }

        public string[] GetCharacterNames(string accountName, string realmName)
        {
            if (accountName == null)
                throw new ArgumentNullException("accountName");
            if (realmName == null)
                throw new ArgumentNullException("realmName");
            if (!this.IsDetected)
                throw new InvalidOperationException(Strings.InvalidOperationWorldOfWarcraftNotDetected);

            // Get the path to the realm
            string realmPath = GetRealmPath(accountName, realmName);

            // Return all subdirectory names (representing characters)
            return Directory.GetDirectories(realmPath).Select(directory => Path.GetFileName(directory)).ToArray();
        }

        /// <summary>
        /// Returns the path to the addon.
        /// </summary>
        /// <param name="addOnName">The name of the addon.</param>
        /// <returns>
        /// A <see cref="string"/> containing the path to the addon.
        /// </returns>
        public string GetAddOnPath(string addOnName)
        {
            if (addOnName == null)
                throw new ArgumentNullException("addOnName");
            if (!this.IsDetected)
                throw new InvalidOperationException(Strings.InvalidOperationWorldOfWarcraftNotDetected);

            return this.InstallPath
                .Combine(relativeAddOnPath)
                .Combine(addOnName);
        }

        /// <summary>
        /// Returns the path to the SavedVariables file for an addon.
        /// </summary>
        /// <param name="accountName">The name of the account used.</param>
        /// <param name="addOnName">The name of the addon.</param>
        /// <returns>
        /// A <see cref="string"/> containing the path to the SavedVariables file.
        /// </returns>
        public string GetSavedVariablesPath(string accountName, string addOnName)
        {
            if (accountName == null)
                throw new ArgumentNullException("accountName");
            if (addOnName == null)
                throw new ArgumentNullException("addOnName");
            if (!this.IsDetected)
                throw new InvalidOperationException(Strings.InvalidOperationWorldOfWarcraftNotDetected);

            // Get the path to the account
            string accountPath = GetAccountPath(accountName);

            // Get the path to the directory holding the SavedVariables files
            string savedVariablesPath = Path.Combine(accountPath, savedVariablesRelativePath);

            // Return the correct SavedVariables path
            return Path.Combine(savedVariablesPath, addOnName + luaExtension);
        }

        /// <summary>
        /// Returns the path to the SavedVariablesPerCharacter file for a character and addon.
        /// </summary>
        /// <param name="accountName">The name of the account.</param>
        /// <param name="realmName">The name of the realm.</param>
        /// <param name="characterName">The name of the character.</param>
        /// <param name="addOnName">The name of the addon.</param>
        /// <returns>
        /// A <see cref="string"/> containing the path to the SavedVariablesPerCharacter file.
        /// </returns>
        public string GetSavedVariablesPerCharacterPath(string accountName, string realmName, string characterName, string addOnName)
        {
            if (accountName == null)
                throw new ArgumentNullException("accountName");
            if (realmName == null)
                throw new ArgumentNullException("realmName");
            if (characterName == null)
                throw new ArgumentNullException("characterName");
            if (addOnName == null)
                throw new ArgumentNullException("addOnName");

            // Get the path to the account
            string accountPath = Path.Combine(Path.Combine(this.InstallPath, relativeAccountPath), accountName);
            if (!Directory.Exists(accountPath))
                throw new ArgumentException(Strings.AccountNotFound, "accountName");

            // Get the path to the character
            string characterPath = GetCharacterPath(accountName, realmName, characterName);

            // Get the path to the directory holding the SavedVariables files
            string savedVariablesPath = Path.Combine(characterPath, savedVariablesRelativePath);

            // Return the correct SavedVariablesPerCharacter path
            return Path.Combine(savedVariablesPath, addOnName + luaExtension);

        }

        /// <summary>
        /// Returns a value indicating whether an addon with the name is installed.
        /// </summary>
        /// <param name="addOnName">The name of the addon.</param>
        /// <returns>
        /// True, if the addon is installed; False otherwise.
        /// </returns>
        public bool IsAddOnInstalled(string addOnName)
        {
            if (addOnName == null) 
                throw new ArgumentNullException("addOnName");
            if (!this.IsDetected)
                throw new InvalidOperationException(Strings.InvalidOperationWorldOfWarcraftNotDetected);

            // Get the path to the addon
            string addOnPath = this.GetAddOnPath(addOnName);

            // Check whether the addon's directory exists
            if (!Directory.Exists(addOnPath))
                return false;

            // Construct the path to the addon's TOC file
            string addOnTocPath = this.GetAddOnTocPath(addOnName);

            // Check whether the addon's TOC file exists
            if (!File.Exists(addOnTocPath))
                return false;

            // All checks passed, return true
            return true;
        }

        /// <summary>
        /// Returns the version of the addon.
        /// </summary>
        /// <param name="addOnName">The name of the addon.</param>
        /// <returns>
        /// An instance of the <see cref="Version"/> class containing the version of the addon.
        /// </returns>
        public Version GetAddOnVersion(string addOnName)
        {
            if (addOnName == null) 
                throw new ArgumentNullException("addOnName");
            if (!this.IsDetected)
                throw new InvalidOperationException(Strings.InvalidOperationWorldOfWarcraftNotDetected);
            if (!IsAddOnInstalled(addOnName))
                throw new ArgumentException(Strings.AddOnNotInstalled, "addOnName");

            // Get the path to the addon's TOC file
            string addOnTocPath = this.GetAddOnTocPath(addOnName);

            // Open the TOC file for reading
            using (StreamReader streamReader = File.OpenText(addOnTocPath))
            {
                // Read the file while not EOF
                while (!streamReader.EndOfStream)
                {
                    // Read the next line and trim any leading and trailing white-spaces
                    string tocLine = streamReader.ReadLine().Trim();

                    // Check whether the line is defining the version
                    if (tocLine.StartsWith("##", StringComparison.Ordinal) && tocLine.Contains("Version:"))
                    {
                        // Take the version as string
                        string version = tocLine.Substring(tocLine.IndexOf(':') + 1).Trim();

                        // Parse the version into a Version instance
                        try
                        {
                            return new Version(version);
                        }
                        catch (ArgumentException)
                        {
                        }
                        catch (FormatException)
                        {
                        }
                        catch (OverflowException)
                        {
                        }
                    }
                }
            }

            // Return default version instance
            return new Version();
        }

        private string GetAddOnTocPath(string addOnName)
        {
            if (addOnName == null) 
                throw new ArgumentNullException("addOnName");

            // Get the path to the addon
            string addOnPath = this.GetAddOnPath(addOnName);

            // Construct the path to the addon's TOC file and return it
            return Path.Combine(addOnPath, Path.GetFileName(addOnPath) + tocExtension);
        }

        private string GetAccountPath(string accountName)
        {
            string accountPath = Path.Combine(Path.Combine(this.InstallPath, relativeAccountPath), accountName);
            if (!Directory.Exists(accountPath))
                throw new ArgumentException(Strings.AccountNotFound, "accountName");
            return accountPath;
        }

        private string GetRealmPath(string accountName, string realmName)
        {
            string accountPath = GetAccountPath(accountName);

            string realmPath = Path.Combine(accountPath, realmName);
            if (!Directory.Exists(realmPath))
                throw new ArgumentException(Strings.RealmNotFound, "realmName");
            return realmPath;
        }

        private string GetCharacterPath(string accountName, string realmName, string characterName)
        {
            string realmPath = GetRealmPath(accountName, realmName);

            string characterPath = Path.Combine(realmPath, characterName);
            if (!Directory.Exists(characterPath))
                throw new ArgumentException(Strings.CharacterNotFound, "characterName");

            return characterPath;
        }
    }
}